package com.fduss.mazegame.core.maze;

import com.fduss.mazegame.core.Update;
import com.fduss.mazegame.core.controller.MonsterCtrl;
import com.fduss.mazegame.core.groupunit.Trail;
import com.fduss.mazegame.core.groupunit.TrailArray;
import com.fduss.mazegame.core.groupunit.TrailFake;
import com.fduss.mazegame.core.unit.*;
import com.fduss.mazegame.core.unit.feature.HasEvent;
import com.fduss.mazegame.core.unit.feature.Icon;
import com.fduss.mazegame.server.ClientView;
import com.fduss.mazegame.util.Direction;
import com.fduss.mazegame.util.Queue;
import com.fduss.mazegame.util.QueueLinked;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * This is the main class of the game.
 * <p>
 * Its most simple implementation holds a maze, an end and a hero and
 * take care of moving the hero around and geenrating the ClientView
 * (the data structure that clients consume to render the maze to the
 * player).
 *
 * @Date 2017/9/27
 * @Time 15:00
 * @Author duocai wu
 */
public class Maze {

    private Map map;
    private End end;
    private Hero hero;
    private List<Monster> monsters = new ArrayList<>();
    private Trail trail;
    private Queue<Update> updates;

    public Maze(Map map, End end, Hero hero, Monster[] monsters, int trailCapacity) {
        if (map == null) {
            throw new NullPointerException("maze");
        }
        if (end == null) {
            throw new NullPointerException("end");
        }
        if (hero == null) {
            throw new NullPointerException("hero");
        }
        if (trailCapacity < 0) {
            throw new IllegalArgumentException("trailCapacity < 0");
        }
        this.map = map;
        this.end = end;
        this.hero = hero;
        // init monsters
        this.monsters.addAll(Arrays.asList(monsters));
        if (trailCapacity == 0) {
            this.trail = new TrailFake();
        } else {
            this.trail = new TrailArray(trailCapacity);
        }
        updates = new QueueLinked<>();

        // start monster auto moving
        new Thread(new MonsterCtrl(this, this.monsters)).start();
    }

    /**
     * move the hero towards the parameter direction
     *
     * @param dir the target direction
     */
    public boolean moveHero(Direction dir) {
        Place old = hero.getPlace();
        boolean lastMoveOk = hero.move(dir);

        if (lastMoveOk) {
            hero.addSteps();
            // records the updated place, so we could only repaint the updated place
            // int GUI case, this will be useful.
            setHeroAndTrailIconUpdates(old);
            // trigger event: decide whether the hero is alive
            triggerEvents(hero.getPlace(), hero);
        }

        return lastMoveOk;
    }

    public boolean moveMonster(Monster monster, Direction dc) {
        Place old = monster.getPlace();
        boolean ok = monster.move(dc);
        if (ok) {
            updates.enqueue(new Update(old, old.getIcon()));
            updates.enqueue(new Update(monster.getPlace(), monster.getPlace().getIcon()));
            triggerEvents(monster.getPlace(), monster);
        }
        return ok;
    }

    private void setHeroAndTrailIconUpdates(Place old) {
        // update to remove old hero
        Place oldHeroPlace = map.getPlace(old.getRow(), old.getCol());
        Update oldHero = new Update(old, oldHeroPlace.getIcon());
        updates.enqueue(oldHero);
        // add old position to trail
        trail.add(old);
        // add trail changes
        trail.update(updates);
        // update to add new hero
        Update newHero = new Update(hero.getPlace(), hero.getPlace().getIcon());
        updates.enqueue(newHero);
    }

    private void triggerEvents(Place dst, HasEvent mob) {
        // trigger event on this place
        int num = dst.getEntiesNum();
        for (int i = 0; i < num; i++) {
            HasEvent ob = dst.getEntity(i);
            ob.handleEvent(this, mob);
        }
    }

    public ClientView getClientView() {
        // get floor maze
        Icon[][] icons = map.getIcons();
        // isGameOver
        boolean isGameOver = isGameOver();
        // isHeroAlive
        boolean isHeroAlive = isHeroAlive();
        return new ClientView(icons, isGameOver, isHeroAlive);
    }

    public boolean isHeroAlive() {
        return hero.isAlive();
    }

    public boolean isGameOver() {
        return hero.getPlace().equals(end.getPlace());
    }

    public Queue<Update> getUpdates() {
        Queue<Update> old = updates;
        // clear all old updates
        updates = new QueueLinked<>();
        return old;
    }

    public boolean killMonster(Direction d) {
        Place dst = hero.getPlace().placeAt(d);
        if (dst == null) {
            return false;
        }

        boolean kill = false;
        for (int i = 0; i < dst.getEntiesNum(); i++) {
            HasEvent ob = dst.getEntity(i);
            if (ob instanceof Monster) {
                Monster monster = (Monster) ob;
                Update update = new Update(dst, Icon.EMPTY);
                updates.enqueue(update);
                this.monsters.remove(monster);
                kill = true;
                hero.addKilling();
                dst.rmEntity(monster);
                i--;
            }
        }
        return kill;
    }

    public boolean pickTreasure(Direction d) {
        Place dst = hero.getPlace().placeAt(d);
        if (dst == null) {
            return false;
        }

        boolean pick = false;
        for (int i = 0; i < dst.getEntiesNum(); i++) {
            HasEvent ob = dst.getEntity(i);
            if (ob instanceof Treasure) {
                Treasure treasure = (Treasure) ob;
                Update update = new Update(dst, Icon.EMPTY);
                updates.enqueue(update);
                pick = true;
                hero.addPicking();
                dst.rmEntity(treasure);
                i--;
            }
        }
        return pick;
    }

    public Hero checkScore() {
        return hero;
    }

    public Map getMap() {
        return map;
    }
}
